package edu.gmu.streams;

import java.io.*;

/**
 * LittleEndianOutputStream is a DataOutput.  Borrowed liberally from
 * Elliot Rusty Harold's Java IO Book.
 * @author  James H. Pope
 * @version $Revision:$ $Date:$
 * @see     java.io.DataOutputStream
 */

public class LittleEndianOutputStream extends FilterOutputStream
  implements DataOutput
{

  /**
   * The number of bytes written so far to the little-endian output stream.
   */
  protected int written;

  /**
   * Creates a new little-endian output stream and chains it to the output
   * stream specified by the out argument.
   *
   * @param  out the underlying output stream.
   * @see    java.io.FilterOutputStream#out
   */
  public LittleEndianOutputStream(OutputStream out)
  {
    super(out);
  }

  /**
   * Writes the specified byte value to the underlying output stream.
   *
   * @param     b  the <code>byte</code> value to be written.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public synchronized void write(int b) throws IOException
  {
    out.write(b);
    written ++;
  }

  /**
   * Writes <code>length</code> bytes from the specified byte array
   * starting at <code>offset</code> to the underlying output stream.
   *
   * @param     data         the data.
   * @param     offset       the start offset in the data.
   * @param     length       the number of bytes to write.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public synchronized void write(byte[] data, int offset, int length)
    throws IOException
  {
    out.write(data,offset,length);
    written += length;
  }


  /**
   * Writes a <code>boolean</code> to the underlying output stream as
   * a single byte. If the argument is true, the byte value 1 is written.
   * If the argument is false, the byte value <code>0</code> is written.
   *
   * @param     b  the <code>byte</code> value to be written.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public void writeBoolean(boolean b) throws IOException
  {
    if (b)
    {
      this.write(1);
    }
    else
    {
      this.write(0);
    }
  }


  /**
   * Writes out a <code>byte</code> to the underlying output stream.
   *
   * @param     b  the <code>byte</code> value to be written.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public void writeByte(int b) throws IOException
  {
    out.write(b); //perhaps should call this.write(b) - synchronized
    written ++;
  }


  /**
   * Writes a two byte <code>short</code> to the underlying output stream
   * in little-endian order, low byte first.
   *
   * @param     s  the <code>short</code> value to be written.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public void writeShort(int s) throws IOException
  {
    out.write(s & 0xFF);
    out.write((s >>> 8) & 0xFF);
    written += 2;
  }



  /**
   * Writes a two byte <code>char</code> to the underlying output stream
   * in little-endian order, low byte first.
   *
   * @param     c  the <code>char</code> value to be written.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public void writeChar(int c) throws IOException
  {
    out.write(c & 0xFF);
    out.write((c >>> 8) & 0xFF);
    written += 2;
  }


  /**
   * Writes a four byte <code>int</code> to the underlying output stream
   *
   * @param     i  the <code>int</code> value to be written.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public void writeInt(int i) throws IOException
  {
    out.write(i & 0xFF);
    out.write((i >>> 8) & 0xFF);
    out.write((i >>> 16) & 0xFF);
    out.write((i >>> 24) & 0xFF);
    written += 4;
  }


  /**
   * Writes a eight byte <code>long</code> to the underlying output stream
   *
   * @param     l  the <code>long</code> value to be written.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public void writeLong(long l) throws IOException
  {
    out.write((int) l & 0xFF);
    out.write((int) (l >>> 8) & 0xFF);
    out.write((int) (l >>> 16) & 0xFF);
    out.write((int) (l >>> 24) & 0xFF);
    out.write((int) (l >>> 32) & 0xFF);
    out.write((int) (l >>> 40) & 0xFF);
    out.write((int) (l >>> 48) & 0xFF);
    out.write((int) (l >>> 56) & 0xFF);
    written += 8;
  }


  /**
   * Writes a four byte Java <code>float</code> to the underlying output stream.
   *
   * @param     f  the <code>float</code> value to be written.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public final void writeFloat(float f) throws IOException
  {
    this.writeInt(Float.floatToIntBits(f));
  }


  /**
   * Writes a eight byte Java <code>double</code> to the underlying output
   * stream.
   *
   * @param     d  the <code>double</code> value to be written.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public final void writeDouble(double d) throws IOException
  {
    this.writeLong(Double.doubleToLongBits(d));
  }


  /**
   * Writes a string to the underlying output stream as a sequence of
   * bytes. Each character is written to the data output stream as if
   * by the <code>writeByte()</code> method.
   *
   * @param     s  the <code>String</code> value to be written.
   * @exception IOException  if the underlying stream throws an exception.
   * @see       java.io.LittleEndianOutputStream#writeByte(int)
   * @see       java.io.LittleEndianOutputStream#out
   */
  public void writeBytes(String s) throws IOException
  {
    int length = s.length();

    for(int i = 0; i < length; i++)
    {
      out.write((byte) s.charAt(i)); //perhaps use this.writeByte - performance
    }
    written += length;
  }



  /**
   * Writes a string to the underlying output stream as a sequence of
   * characters. Each character is written to the data output stream as if
   * by the <code>writeChar()</code> method.
   *
   * @param     s  the <code>String</code> value to be written.
   * @exception IOException  if the underlying stream throws an exception.
   * @see       java.io.LittleEndianOutputStream#writeChar(int)
   * @see       java.io.LittleEndianOutputStream#out
   */
  public void writeChars(String s) throws IOException
  {
    int length = s.length();

    for(int i = 0; i < length; i++)
    {
      int c = s.charAt(i);  // charAt returns a char not an int
      out.write(c & 0xFF);  // perhaps just this.write(char c) - performance
      out.write((c >>> 8) & 0xFF);
    }
    written += 2;
  }


  /**
   * Writes a string of no more than 65,535 characters to the underlying
   * output stream using little-endian UTF-8 encoding. This method first
   * writes a two-byte short in little-endian order as if by the
   * <code>writeShort</code> method. This gives the number of bytes in
   * the UTF-8 encoded version of the string, not the number of characters
   * in the string. Next each character of the string is written using
   * the little-endian UTF-8 encoding for the character.
   *
   * @param     s  the <code>String</code> to be written.
   * @exception UTFDataFormatException  if the String is longer than 65,535
   *            characters.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public void writeUTF(String s) throws IOException
  {
    int strlen = s.length();
    int utflen = 0;

    // Determine length first in order to append front
    for(int i = 0; i < strlen; i++)
    {
      //charAt returns a char not an int - implicit conversion
      int c = s.charAt(i);

      // 7 bits = 128 one byte
      if((c >= 0x0001) && (c <= 0x007F))
      {
        utflen ++;
      }
      // 16 bits = 65536 three bytes
      else if(c > 0x07FF)
      {
        utflen += 3;
      }
      // 11 bits = 2048 two bytes
      else
      {
        utflen += 2;
      }
    }

    // Because we append the front with two bytes the max length of the stream
    // cannot be more than 2 to the 16 = 65536 (0 to 65535).
    if(utflen > 65535)
      throw new UTFDataFormatException();

    /*
     * Now that we have a legal length, append front.
     *
     * Note that we write the length in big-endian.  The UTF-8
     * standard is a byte-for-byte standard (essentially big-endian),
     * thus a "big-endian utf-8 string" and a "little-endian utf-8 string"
     * are exactly the same.
     */
    out.write((utflen >>> 8) & 0xFF);
    out.write(utflen & 0xFF);


    // Now loop through writing out the one, two, or three bytes
    for(int i = 0; i < strlen; i++)
    {
      int c = s.charAt(i); // charAt returns a char not an int - implicit

      // Since write method already increments written by one, we don't need
      // to do anything for one, add 2 for three, and add 1 for two bytes.

      // One byte 7 bits
      if((c >= 0x0001) && (c <= 0x007F))
      {
        // Header should be 0XXXXXXX
        out.write(c);
      }
      // Three bytes 16 bits
      else if(c > 0x07FF)
      {
        //  - we are going from two to three
        // Need to add a header to each byte - 10XXXXXX 10XXXXXX 1110XXXX
        //
        // First byte                      Second byte
        // c =        XXXXXXXX XXXXXXXX    c =        XXXXXXXX XXXXXXXX
        // c & 3F     00000000 00111111    >>>6       000000XX XXXXXXXX
        //            -----------------    c & 3F     00000000 00111111
        //            00000000 00XXXXXX               -----------------
        // | 80       00000000 10000000               00000000 00XXXXXX
        //            -----------------    | 80       00000000 10000000
        //            00000000 10XXXXXX               -----------------
        //                                            00000000 10XXXXXX
        //
        // Third byte
        // c =        XXXXXXXX XXXXXXXX
        // >>>12      00000000 0000XXXX
        // c & 0F     00000000 00001111 not sure if we really need this step
        //            -----------------
        //            00000000 0000XXXX
        // | E0       00000000 11100000
        //            -----------------
        //            00000000 1110XXXX
        out.write(0x80 | (c & 0x3F));
        out.write(0x80 | ((c >>> 6) & 0x3F));
        out.write(0xE0 | ((c >>> 12) & 0x0F));
        written += 2;
      }
      // Two bytes 11 bits
      else
      {
        // c = 00000XXX XXXXXXXX - we are going from two to two
        // Need to add a header to each byte - 10XXXXXX 110XXXXX
        //
        // First byte                      Second byte
        // c =        00000XXX XXXXXXXX    c =        00000XXX XXXXXXXX
        // c & 3F     00000000 00111111    >>>6       00000000 000XXXXX
        //            -----------------    c & 1F     00000000 00011111
        //            00000000 00XXXXXX               -----------------
        // | 80       00000000 10000000               00000000 000XXXXX
        //            -----------------    | C0       00000000 11000000
        //            00000000 10XXXXXX               -----------------
        //                                            00000000 110XXXXX
        out.write(0x80 | (c & 0x3F));
        out.write(0xC0 | (c >>> 6) & 0x1F);
        written += 1;
      }
    }
    written += strlen + 2;

  }



  /**
   * Returns the number of bytes written to this little-endian output stream.
   * (This class is not thread-safe with respect to this method. It is
   * possible that this number is temporarily less than the actual number
   * of bytes written.)
   *
   * @return    the value of the <code>written</code> field.
   * @see       java.io.LittleEndianOutputStream#written
   */
  public int size()
  {
    return this.written;
  }


}

